﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;

namespace PruebaLuaCF
{
    class Program
    {
        static Dictionary<string, Lua.lua_CFunction> funciones =
            new Dictionary<string, Lua.lua_CFunction>();

        static string programa2 =
            "local sss = string.rep(\"a\", 16384) \r\n" +
            "local rim = regexIsMatch   \r\n" +
            "function main ()           \r\n" +
            "   if (rim ('$INPUT', '$PATTERN')) then \r\n" +
            "       return 'si-match'   \r\n" +
            "   else                    \r\n" +
            "       return 'no'         \r\n" +
            "   end                     \r\n" +
            "end                        \r\n" +
            "                           \r\n" +
            "function fff ()            \r\n" +
            "   return 'hola'           \r\n" +
            "end                        \r\n" +
            "                           \r\n";

        static int regexIsMatch (IntPtr L)
        {
            //  tiene dos parámetros (vienen al reve)
            //
            var pattern = Lua.lua_tostring(L, -1);
            Lua.lua_pop(L);
            var input   = Lua.lua_tostring(L, -1);
            Lua.lua_pop(L);

            //  construyo el retorno
            bool isMatch = false; //Regex.IsMatch(input, pattern);

            //  retorno
            Lua.lua_pushboolean(L, isMatch);

            //  num rets 1
            return 1;
        }

        static int damedni (IntPtr L)
        {
            Lua.lua_pushnumber(L, 28146297);
            return 1; // <- nro de valores de retorno (puedo retornar muchos, es lua)
        }
        
        static int mimoDNI (IntPtr L)
        {
            Lua.lua_pushnumber(L, 28146297);
            return 1; // <- cantidad de valores de retorno:
            // en lua se pueden retornar múltiples valores.
        }

        static void registrarFuncion (
            IntPtr L, string nombre, Lua.lua_CFunction fn 
        )
        {
            Lua.lua_pushcfunction (L, fn);
            Lua.lua_setglobal     (L, nombre);
        }

        static void Main (string [] args)
        {
            // 8 microsegs por llamado a función externa
            // (probado con 10 millones de iteraciones) 
            // en un solo procesador de 2.31 Ghz

            var L = Lua.luaL_newstate();
            Lua.luaL_openlibs(L);

            // creo la func
            var func = new Lua.lua_CFunction(regexIsMatch);
            var keyRegexIsMatch = "PruebaLuaCF.Program::regexIsMatch";
            funciones.Add(keyRegexIsMatch, func);

            // pongo la func
            registrarFuncion(
                L, "regexIsMatch", funciones[keyRegexIsMatch]
            );

            // cargo el programa que usa la func
            programa2 = programa2
                .Replace("$INPUT", @"x")
                .Replace("$PATTERN", "^[0-9]{2}");
            byte[] buff = Encoding.ASCII.GetBytes(programa2);
            int loadRet = Lua.luaL_loadstring(L, buff);

            if (loadRet != 0)
            {
                Console.WriteLine   (Lua.lua_tostring(L, -1));
                mostrarErrorYSalir  (L);
            }

            int pcallRet1 = Lua.lua_pcall(L, 0, 0, 0);

            if (0 != pcallRet1)
            {
                Console.WriteLine(Lua.lua_tostring(L, -1));
                mostrarErrorYSalir(L);
            }

            var start = Environment.TickCount;
            //var veces = (int) 1e4; // 10.000
            var veces = int.MaxValue;

            Console.WriteLine("Hola!");

            int start1000 = Environment.TickCount;

            for (int i = 0; i < (int) veces; ++i)
            {
                if (((i % 1000) == 0) && (i != 0))
                {
                    Console.WriteLine("1000 en {0} milis", Environment.TickCount - start1000);
                    Console.WriteLine("GC {0} KB usados", Lua.lua_gc(L, Lua.LUA_GCCOUNT, 0));
                    start1000 = Environment.TickCount;
                }

                Lua.lua_getglobal(L, "main");

                if (! Lua.lua_isfunction(L, -1))
                {
                    Console.WriteLine("arreglando en {0}", i);
                    int startArreglo = Environment.TickCount;
                    // popeo el NIL
                    Lua.lua_pop(L); 

                    // empujo la función "regexIsMatch"
                    // al interprete porque me la chupó
                    // el GC
                    // registrarFuncion (
                    //    L, "regexIsMatch", funciones[keyRegexIsMatch]
                    //);

                    // empujo la función "main"
                    // a la pila para usarla
                    Lua.lua_getglobal(L, "main");
                    if (!Lua.lua_isfunction(L, -1))
                        throw new Exception("No se pudo recargar la funcion o la funcion no existe!");

                    int endArreglo = Environment.TickCount;
                    Console.WriteLine("Arreglado en {0} milis", endArreglo - startArreglo);
                }

                // averiguar el tipo aca...
                int pcallRet2 = Lua.lua_pcall(L, 0, 1, 0);

                if (0 != pcallRet2)
                    mostrarErrorYSalir(L);

                if (Lua.lua_isstring_b(L, -1))
                {
                    if (Lua.lua_tostring(L, -1).IndexOf("no") == -1)
                        throw new Exception("La cadena no tiene el formato esperado");

                    Lua.lua_pop(L);
                }
                else
                {
                    throw new Exception("No hay una cadena en la respuesta de la función");
                }
            }

            var ret = Environment.TickCount - start;
            Console.WriteLine("{0,12} llamados a función", veces);
            Console.WriteLine("{0,12} milis en total", ret);
            Console.WriteLine("{0,12} milis por llamado", (double)ret / (double)veces);
            Thread.Sleep(1000);
            funciones.Remove(keyRegexIsMatch);
            Lua.lua_close(L);
        }
        
        static void mostrarErrorYSalir (IntPtr L)
        {
            stackDump           (L);
            //Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine   (Lua.lua_tostring(L, -1));
            //Console.ForegroundColor = ConsoleColor.Gray;
            Lua.lua_close       (L);
            Console.ReadLine    ();
            //Environment.Exit    (-1);
        }

        static void stackDump (IntPtr L)
        {
            Console.WriteLine("------------------------");
            int top = Lua.lua_gettop(L);

            for (int i = 1; i <= top; i++)
            {  /* repeat for each level */
                int t = Lua.lua_type(L, i);
                switch (t)
                {
                    case Lua. LUA_TNONE:
                        Console.WriteLine("NONE");
                        break;
                    case Lua. LUA_TNIL:
                        Console.WriteLine("NIL");
                        break;
                    case Lua. LUA_TBOOLEAN:
                        Console.WriteLine("BOOLEAN {0}", Lua.lua_toboolean(L, i) == 1);
                        break;
                    case Lua. LUA_TLIGHTUSERDATA:
                        Console.WriteLine("LIGHT_USER_DATA");
                        break;
                    case Lua. LUA_TNUMBER:
                        Console.WriteLine("NUMBER {0}", Lua.lua_tonumber(L, i));
                        break;
                    case Lua. LUA_TSTRING:
                        Console.WriteLine("STRING {0}", Lua.lua_tostring(L, i));
                        break;
                    case Lua. LUA_TFUNCTION:
                        Console.WriteLine("FUNCION");
                        break;
                    case Lua.LUA_TTABLE:
                    case Lua. LUA_TUSERDATA:
                    case Lua. LUA_TTHREAD:
                    case Lua. LUA_NUMTAGS:
                        Console.WriteLine("NO_LO_HICE!");
                        break;
                    default:
                        Console.WriteLine("DEFAULT_XXX");
                        break;
                }
                Console.WriteLine();
            }
            Console.WriteLine();
        }
    }
}
